﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using MPI;

namespace Dwarf.Managed
{
    /// <summary>
    /// User interface for solving the Graph models Problem.
    /// </summary>
    class Program
    {
        /// <summary>
        /// The settings for this dwarf.
        /// </summary>
        static private Settings settings;

        /// <summary>
        /// Get settings from the command line.
        /// </summary>
        /// <param name="args">
        /// Cli params.
        /// </param>
        /// <param name="solver">
        /// Instance of Solver.
        /// </param>
        static void getSettings(String[] args, Solver solver)
        {
            bool error = false;
            if (solver.isRootThread)
            {
                try
                {
                    settings = new Settings();
                    settings.Init(args, solver);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    error = true;
                }
            }
            //Distribute error if that occurred while parsing.
            Communicator.world.Broadcast(ref error, Settings.ROOT_PROCESS);
            if (error) throw new Exception("");

            int n = 0;
            int m = 0;
            int t = 0;
            if (solver.isRootThread)
            {
                n = solver.hmm.getNumberOfStates();
                m = solver.hmm.getNumberOfObservation();
                t = solver.vit.getLengthOfObservationSequence();
            }
            //Distribute the number of states.
            Communicator.world.Broadcast(ref n, Settings.ROOT_PROCESS);
            //Distribute the number of observation.
            Communicator.world.Broadcast(ref m, Settings.ROOT_PROCESS);
            //Distribute the length of observation sequence.
            Communicator.world.Broadcast(ref t, Settings.ROOT_PROCESS);

            //Initializing of slave instances
            if (!solver.isRootThread)
            {
                solver.init(n, m, t);
            }

            //Distribute  the A and B matrices to all instances
            int lengthOfStates = solver.hmm.getNumberOfStates();
            for (int i = 0; i < lengthOfStates; i++)
            {
                Communicator.world.Broadcast(ref solver.hmm.stateTransitionMatrix[i], Settings.ROOT_PROCESS);
                Communicator.world.Broadcast(ref solver.hmm.observationProbabilityMatrix[i], Settings.ROOT_PROCESS);
            }

            //Distribute  O vector to all instances.
            Communicator.world.Broadcast(ref solver.vit.observationSequence, Settings.ROOT_PROCESS);

            /*Set up of balance*/
            if (Communicator.world.Rank == (Communicator.world.Size - 1))
            {
                solver.lengthForProc = solver.hmm.getNumberOfStates() - (solver.hmm.getNumberOfStates() / Communicator.world.Size) * Communicator.world.Rank;
                solver.startIndx = solver.hmm.getNumberOfStates() - solver.lengthForProc;
                solver.endIndx = solver.hmm.getNumberOfStates();
            }
            else
            {
                solver.lengthForProc = solver.hmm.getNumberOfStates() / Communicator.world.Size;
                solver.startIndx = solver.lengthForProc * Communicator.world.Rank;
                solver.endIndx = solver.lengthForProc * (Communicator.world.Rank + 1);
            }
            solver.arrayOflengthForProc = new int[Communicator.world.Size];
            Communicator.world.Gather(solver.lengthForProc, Settings.ROOT_PROCESS,ref solver.arrayOflengthForProc);
            Communicator.world.Broadcast(ref solver.arrayOflengthForProc, Settings.ROOT_PROCESS);
            /*End of setup*/
        }

        /// <summary>
        /// Point of the program start.
        /// </summary>
        /// <param name="args">
        /// Cli params.
        /// </param>
        static void Main(String[] args)
        {

            try
            {
                using (new MPI.Environment(ref args))                       // MPI Initialization.
                {
                    if (Communicator.world.Size == 1)
                    {
                        throw new Exception("Only one mpi process.");
                    }

                    Solver solver = new Solver();

                    getSettings(args, solver);                              // Get settings from the command line.

                    Communicator.world.Barrier();
                    if (Communicator.world.Rank == Settings.ROOT_PROCESS)
                        settings.Start();                                   // Start new time count.
                    solver.Solve();                                         // Solve the current problem.
                    Communicator.world.Barrier();
                    if (Communicator.world.Rank == Settings.ROOT_PROCESS)
                        settings.Finish(solver);                            // Stop the time count and write results.

                }

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }

        }
    }
}
